package com.demo.security.access.method;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.method.AbstractFallbackMethodSecurityMetadataSource;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;

import com.demo.security.access.model.SecurityResource;
import com.demo.security.core.SecurityMetadataSourceResolver;
import com.demo.security.impl.MethodSecurityMetadataSourceHandler;

/**
 * 持久化的基于XML的方法安全元数据资源类 提供方法-安全配置属性对应Map 该类提供
 * {@link MethodSecurityMetadataSource} 实现。 该类参照
 * {@link org.springframework.security.access.method.MapBasedMethodSecurityMetadataSource}
 * 提供更细粒度的安全方法
 * 
 * @author ryuu.kk
 * 
 */
public class PersistentMapBasedMethodSecurityMetadataSource extends	AbstractFallbackMethodSecurityMetadataSource {

	// ~ Instance fields
	// ================================================================================================
	/**class loader：current thread**/
	private ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader();

	/** Map from RegisteredMethod to ConfigAttribute list */
	protected final Map<RegisteredMethod, List<ConfigAttribute>> methodMap = new HashMap<RegisteredMethod, List<ConfigAttribute>>();

	/** Map from RegisteredMethod to name pattern used for registration */
	private final Map<RegisteredMethod, String> nameMap = new HashMap<RegisteredMethod, String>();

	/** 安全元数据分解器 **/
	private SecurityMetadataSourceResolver handler;
	
	/** 方法安全资源模型列表 **/
	private List<SecurityResource> methodRescs;

	// ~ Methods
	// ========================================================================================================
	/**
	 * 构造器
	 * @param methodRescs 方法资源
	 */
	public PersistentMapBasedMethodSecurityMetadataSource(List<SecurityResource> methodRescs) {
		this.methodRescs = methodRescs;
		this.handler = new MethodSecurityMetadataSourceHandler();
		for (Map.Entry<String, List<ConfigAttribute>> entry : this.handler.resolver(this.methodRescs).entrySet()) {
			addSecureMethod(entry.getKey(), entry.getValue());
		}
	}

	/**
	 * 通过安全方法元数据分解器，生成安全方法资源列表生成Map
	 * @param methodRescs 方法资源模型
	 * @param resolver 解析器
	 */
	public PersistentMapBasedMethodSecurityMetadataSource(List<SecurityResource> methodRescs, SecurityMetadataSourceResolver resolver) {
		this.methodRescs = methodRescs;
		this.handler = resolver;

		Assert.notNull(methodRescs, " MethodRescs can not be empty.");

		if (this.handler == null) {
			this.handler = new MethodSecurityMetadataSourceHandler();
		}

		for (Map.Entry<String, List<ConfigAttribute>> entry : this.handler.resolver(this.methodRescs).entrySet()) {
			addSecureMethod(entry.getKey(), entry.getValue());
		}
	}

	/**
	 * 使用Map创建 <tt>MapBasedMethodSecurityMetadataSource</tt> Map的key可带泛名/全名/参数名.
	 * 
	 * @param methodMap 方法Map.
	 */
	public PersistentMapBasedMethodSecurityMetadataSource(Map<String, List<ConfigAttribute>> methodMap) {
		for (Map.Entry<String, List<ConfigAttribute>> entry : methodMap.entrySet()) {
			addSecureMethod(entry.getKey(), entry.getValue());
		}
	}

	/**
	 * Obtains the configuration attributes explicitly defined against this
	 * bean.
	 * 
	 * @return the attributes explicitly defined against this bean
	 */
	public Collection<ConfigAttribute> getAllConfigAttributes() {
		Set<ConfigAttribute> allAttributes = new HashSet<ConfigAttribute>();

		for (List<ConfigAttribute> attributeList : methodMap.values()) {
			allAttributes.addAll(attributeList);
		}

		return allAttributes;
	}

	@Override
	protected Collection<ConfigAttribute> findAttributes(Class<?> clazz) {
		// TODO Auto-generated method stub
		return null;
	}

	/**
	 * 找到类+方法所匹配的安全配置属性
	 * @param method 拦截方法
	 * @param targetClass 目标类
	 * @return 返回安全配置属性集合
	 */
	protected Collection<ConfigAttribute> findAttributes(Method method,	Class<?> targetClass) {
		if (targetClass == null) {
			return null;
		}

		return findAttributesSpecifiedAgainst(method, targetClass);
	}

	/**
	 * 未找到则对超类进行查找,目的是找到与方法最近的一个安全配置属性、未找到返回null
	 * 
	 * @param method 拦截器方法
	 * @param clazz 类
	 * @return 安全配置属性
	 */
	private List<ConfigAttribute> findAttributesSpecifiedAgainst(Method method,
			Class<?> clazz) {
		RegisteredMethod registeredMethod = new RegisteredMethod(method, clazz);
		if (methodMap.containsKey(registeredMethod)) {
			return (List<ConfigAttribute>) methodMap.get(registeredMethod);
		}
		// Search superclass
		if (clazz.getSuperclass() != null) {
			return findAttributesSpecifiedAgainst(method, clazz.getSuperclass());
		}
		return null;
	}

	/**
	 * Add configuration attributes for a secure method. Method names can end or
	 * start with <code>&#42</code> for matching multiple methods.
	 * 
	 * @param name type and method name, separated by a dot
	 * @param attr the security attributes associated with the method
	 */
	protected void addSecureMethod(String name, List<ConfigAttribute> attr) {
		
		int lastDotIndex;
		int argsIndex = name.indexOf("(");
		if (argsIndex == -1) {
			lastDotIndex = name.lastIndexOf(".");
		} else {
			lastDotIndex = name.substring(0, name.indexOf("(")).lastIndexOf(".");
		}

		if (lastDotIndex == -1) {
			throw new IllegalArgumentException("'" + name
					+ "' is not a valid method name: format is FQN.methodName");
		}

		String methodName = name.substring(lastDotIndex + 1);
		Assert.hasText(methodName, "Method not found for '" + name + "'");

		String typeName = name.substring(0, lastDotIndex);
		Class<?> type = ClassUtils.resolveClassName(typeName,
				this.beanClassLoader);

		addSecureMethod(type, methodName, attr);
	}

	/**
	 * 使用方法类型和方法名(参数可选)存储到方法安全Map中 此方法支持泛名
	 * 
	 * @param javaType 安全配置属性的目标接口或类
	 * @param mappedName 存储在安全方法Map的方法(公开的或继承的)
	 * @param attr 安全配置属性列表
	 */
	public void addSecureMethod(Class<?> javaType, String mappedName, List<ConfigAttribute> attr) {
		String name = javaType.getName() + '.' + mappedName;
		String methodName = mappedName;
		if (logger.isDebugEnabled()) {
			logger.debug("Request to add secure method [" + name
					+ "] with attributes [" + attr + "]");
		}

		Method[] methods = javaType.getMethods();
		List<Method> matchingMethods = new ArrayList<Method>();

		int argsIndex = mappedName.indexOf("(");
		String[] args = new String[] {};
		if (argsIndex != -1) {
			// TODO:提供参数版本,需要提供方法参数取得方法
			methodName = mappedName.substring(0, argsIndex);
			String argsString = mappedName.substring(argsIndex + 1,
					mappedName.indexOf(")"));
			args = argsString.split(",");
		}

		for (Method m : methods) {
			if (isMatch(m.getName(), mappedName)) {
				matchingMethods.add(m);
				continue;
			}
			// TODO:处理带有参数的方法。如:addSecureMethod(java.lang.Class<?>,
			// java.util.List)
			boolean isSame = true;
			if (m.getName().equals(methodName)) {
				if (args.length > 0) {
					Class<?>[] argsClass = m.getParameterTypes();
					if (args.length != argsClass.length) {
						//break;
						continue;
					}
					for (int i = 0; i < args.length; i++) {
						Class<?> type = ClassUtils.resolveClassName(args[i].trim(), this.beanClassLoader);
						if (!type.equals(argsClass[i])) {
							isSame = false;
						}
					}
				}
				if (isSame) {
					matchingMethods.add(m);
				}
			}
		}

		if (matchingMethods.isEmpty()) {
			throw new IllegalArgumentException("Couldn't find method '"	+ mappedName + "' on '" + javaType + "'");
		}

		// register all matching methods
		for (Method method : matchingMethods) {
			RegisteredMethod registeredMethod = new RegisteredMethod(method, javaType);
			String regMethodName = (String) this.nameMap.get(registeredMethod);

			if ((regMethodName == null) 
					|| (!regMethodName.equals(name) 
							&& (regMethodName.length() <= name.length()))) {
				// no already registered method name, or more specific
				// method name specification now -> (re-)register method
				if (regMethodName != null) {
					logger.debug("Replacing attributes for secure method ["
							+ method + "]: current name [" + name
							+ "] is more specific than [" + regMethodName + "]");
				}

				this.nameMap.put(registeredMethod, name);
				addSecureMethod(registeredMethod, attr);
			} else {
				logger.debug("Keeping attributes for secure method [" + method
						+ "]: current name [" + name
						+ "] is not more specific than [" + regMethodName + "]");
			}
		}
	}

	/**
	 * Adds configuration attributes for a specific method, for example where
	 * the method has been matched using a pointcut expression. If a match
	 * already exists in the map for the method, then the existing match will be
	 * retained, so that if this method is called for a more general pointcut it
	 * will not override a more specific one which has already been added.
	 * <p>
	 * This method should only be called during initialization of the
	 * {@code BeanFactory}.
	 * 
	 * @param javaType invocation target java Class
	 * @param method invocation target java Method
	 * @param attr security config attribute collection
	 */
	public void addSecureMethod(Class<?> javaType, Method method, List<ConfigAttribute> attr) {
		RegisteredMethod key = new RegisteredMethod(method, javaType);
		boolean reset = false;
		if (methodMap.containsKey(key)) {
			// 如果Key重复,但安全配置属性不同则重新创建。
			List<ConfigAttribute> attrOwner = methodMap.get(key);
			for (ConfigAttribute arrtibute : attr) {
				if (!attrOwner.contains(arrtibute)) {
					attrOwner.add(arrtibute);
					reset = true;
				}
			}
			if (!reset) {
				logger.debug("Method [" + method
						+ "] is already registered with attributes ["
						+ methodMap.get(key) + "]");
				return;
			}
		}
		if (reset) {
			methodMap.remove(key);
		}
		methodMap.put(key, attr);
	}

	/**
	 * Add configuration attributes for a secure method.
	 * 
	 * @param method the method to be secured
	 * @param attr required authorities associated with the method
	 */
	private void addSecureMethod(RegisteredMethod method, List<ConfigAttribute> attr) {
		Assert.notNull(method, "RegisteredMethod required");
		Assert.notNull(attr, "Configuration attribute required");
		if (logger.isInfoEnabled()) {
			logger.info("Adding secure method [" + method
					+ "] with attributes [" + attr + "]");
		}
		this.methodMap.put(method, attr);
	}

	/**
	 * Return if the given method name matches the mapped name. The default
	 * implementation checks for "xxx" and "xxx" matches.
	 * 
	 * @param methodName the method name of the class
	 * @param mappedName the name in the descriptor
	 * 
	 * @return if the names match
	 */
	private boolean isMatch(String methodName, String mappedName) {
		return (mappedName.endsWith("*") && methodName.startsWith(mappedName.substring(0, mappedName.length() - 1)))
				|| (mappedName.startsWith("*") && methodName.endsWith(mappedName.substring(1, mappedName.length())));
	}
	
	/**
	 * set ClassLoader from spring inject
	 * @param beanClassLoader ClassLoader
	 */
	public void setBeanClassLoader(ClassLoader beanClassLoader) {
		Assert.notNull(beanClassLoader, "Bean class loader required");
		this.beanClassLoader = beanClassLoader;
	}

	/**
	 * @return map size (for unit tests and diagnostics)
	 */
	public int getMethodMapSize() {
		return methodMap.size();
	}

	/**
	 * 存储注册方法的类： Stores both the Java Method as well as the Class we obtained
	 * the Method from. This is necessary because Method only provides us access
	 * to the declaring class. It doesn't provide a way for us to introspect
	 * which Class the Method was registered against. If a given Class inherits
	 * and redeclares a method (i.e. calls super();) the registered Class and
	 * declaring Class are the same. If a given class merely inherits but does
	 * not redeclare a method, the registered Class will be the Class we're
	 * invoking against and the Method will provide details of the declared
	 * class.
	 */
	private class RegisteredMethod {
		/** method property **/
		private final Method method;
		/** target class  **/
		private final Class<?> registeredJavaType;
		/**
		 * Constructors
		 * @param method java method class
		 * @param registeredJavaType target class
		 */
		public RegisteredMethod(Method method, Class<?> registeredJavaType) {
			Assert.notNull(method, "Method required");
			Assert.notNull(registeredJavaType, "Registered Java Type required");
			this.method = method;
			this.registeredJavaType = registeredJavaType;
		}
		/**
		 * override equals method that in object class
		 * @param obj income params
		 * @return true/false
		 */
		public boolean equals(Object obj) {
			if (this == obj) {
				return true;
			}
			if (obj != null && obj instanceof RegisteredMethod) {
				RegisteredMethod rhs = (RegisteredMethod) obj;
				return method.equals(rhs.method)
						&& registeredJavaType.equals(rhs.registeredJavaType);
			}
			return false;
		}
		/** hash code method @return result **/
		public int hashCode() {
			return method.hashCode() * registeredJavaType.hashCode();
		}
		/** toString method @return String result **/
		public String toString() {
			return "RegisteredMethod[" + registeredJavaType.getName() + "; "
					+ method + "]";
		}
	}
}
